//ring cooperation class
//$Id: ring_coop.h 605 2010-11-27 15:10:07Z Oleg.Bulychov $
#pragma once


template<class Recv, class Send, class ISend>
class ring_cooperation
{
protected:
    int cooperation_rate_;
    bool async_mode_;

    bool async_coop0_;
public:
    ring_cooperation(): async_coop0_()
    {
        accept(meta::set_default_values());
    }

    static char const* get_Name() { return "coop"; }
    static char const* get_Section() { return get_Name(); }

    template<class V>
    void accept(V& v)
    {
        v(VAR_(async_mode),                 false);
        v(VAR_(cooperation_rate),           25);
    }

    bool is_Async_Mode() const
    {
        return async_mode_;
    }

    int get_Rate() const
    {
        return cooperation_rate_;
    }

    template<class S, class Comm>
    bool cooperate(S& solver, int rank, int prev, int next, int num_proc,
        int step, Comm& comm)
    {
        if (1 == num_proc || !cooperation_rate_)
            return false;

        Recv in(prev, comm);
        if (async_mode_)
        {
            ISend out(next, comm);
            return cooperate_Async(solver, rank, num_proc, step, in, out);
        }
        Send out(next, comm);
        return cooperate_Sync(solver, rank, num_proc, step, in, out);
    }

    template<class S>
    bool cooperate_Async(S& solver, int rank, int num_proc, 
        int step, Recv& in, ISend& out)
    {
        if ((step % cooperation_rate_) == 0)
        {
            if (0 == rank && !async_coop0_)
            {//:start the 'stadium' wave
                async_coop0_ = true;
                if (rank < solver.get_Environment()->debug_level_)
                {
#pragma omp critical(debug_output)
                    std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << ":start coop" << std::endl;
                }
                solver.send_Stop(out);
                solver.emigrate_Async(out);
            }
        }
        if (!in.is_Data_Available())
            return false;
        if (rank < solver.get_Environment()->debug_level_)
        {
#pragma omp critical(debug_output)
            std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << ":***migrant accepted" << std::endl;
        }
        solver.recv_Stop(in);
        //std::cout << rank << ":" << step << ":***recv stop" << std::endl;
        solver.immigrate(in);
        //std::cout << rank << ":" << step << ":***immigrate" << std::endl;
        if (0 != rank)
        {//:continue wave
            if (rank < solver.get_Environment()->debug_level_)
            {
#pragma omp critical(debug_output)
                std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << ":*continue coop" << std::endl;
            }
            solver.send_Stop(out);
            solver.emigrate_Async(out);
        }
        else
            async_coop0_ = false;
        return true;
    }

    template<class S>
    bool cooperate_Sync(S& solver, int rank, int num_proc,
        int step, Recv& in, Send& out)
    {
        if (step % cooperation_rate_)
            return false;
        if (rank < solver.get_Environment()->debug_level_)
        {
#pragma omp critical(debug_output)
            std::cout << std::setw(2) << rank << ":" << std::setw(5) << step << ": sync coop" << std::endl;
        }
        if (num_proc & 1)
        {//:odd - slow
            if (0 == rank)
                solver.emigrate(out);

            solver.immigrate(in);

            if (0 != rank)
                solver.emigrate(out);
        }
        else
        {//:even - faster
            if (rank & 1)
            {
                solver.emigrate(out);
                solver.immigrate(in);
            }
            else
            {
                solver.immigrate(in);
                solver.emigrate(out);
            }
        }
        return true;
    }
};

//EOF!
